package nl.rug.ai.believer.model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

/**
 * This class is responsible for reading a file, and extracting all information from it (to be used
 * by the Believer program).
 * @author S1849700
 *
 */
public class BelieverFileReader {
	
	private State state;
	private State realState;
	
	/**
	 * Main method for this class, used to read a certain file, and put its contents into a state. 
	 * When the reading was successful, the state in the parameter will be overwritten with the 
	 * new content. If unsuccessful, the state in the parameter will remain the same.
	 * True or false is returned to indicate if the file was read successfully. Note that this class
	 * will also do some sanity checks on the input.
	 * @param file - the file to read
	 * @param realState  - the state into which to write the file if it was read succesfully.
	 * @return -- true or false, depending on the success of reading the file.
	 */
	boolean read(String file, State realState) {
		state = new State();
		this.realState = realState;
		String input = getStringFromFile(file);
		String temp[] = input.split("AGENTS"); // ignore all before this marker, so we only use [1]
		if (temp.length != 2) {
			Util.debug("Found a bad number of AGENTS tags");
			return false;
		}
		temp = temp[1].split("WORLDS");
		if (temp.length != 2) {
			Util.debug("Found a bad number of WORLDS tags");
			return false;
		}
		String agents = temp[0];
		temp = temp[1].split("RELATIONS");
		if (temp.length != 2) {
			Util.debug("Found a bad number of RELATIONS tags");
			return false;
		}
		String worlds = temp[0];
		String relations = temp[1];
		try {
			if (extractAgents(agents) && extractWorlds(worlds) && extractRelations(relations)) {
				this.realState.copyFromOther(state);
				return true;	
			}
		}
		catch (RuntimeException rte) {
			throw new RuntimeException("While trying to read input file: " + file + "\n" + 
					rte.getMessage());
		}
		return false;
	}
	
	/**
	 * This method extracts the contents of a file and puts it into a single string.
	 * @param file The file to be read
	 * @return The resulting string
	 */
	private String getStringFromFile(String file) {
		StringBuffer buffer = new StringBuffer();
		try {
			Util.debug("path is: " +((new File(".")).getCanonicalPath()));
			FileReader fr = new FileReader(file);
			BufferedReader br = new BufferedReader(fr);
			int character;
			while ((character = br.read()) != -1) {
				// if we find a comment line, skip it.
				if (buffer.length() != 0 && buffer.charAt(buffer.length() - 1) == '\n' && (char)character == '#') {
					br.readLine();
					continue;
				}
				buffer.append((char)character);
			}
			Util.debug("I now read: " +buffer.toString());
		}
		catch (IOException ioe) {
			Util.debug("error reading file: " + ioe.getMessage());
		}
		return buffer.toString();
	}
	
	/**
	 * Extracts all agents from a string and puts them into agentList
	 * @param agents the string
	 * @return the success of the action
	 */
	private boolean extractAgents(String agents) {
		Util.debug("Received agents: " + agents);
		String temp = agents.replace('\n', ' ');
		String myAgents[] = temp.split(",");
		for (String agent : myAgents) {
			if (agent.trim() == "") {
				Util.debug("Encountered an empty agent string");
				return false;
			}
			try {
				state.addAgent(new Agent(agent.trim()));
			}
			catch (RuntimeException rte) {
				throw new RuntimeException("while reading agents: \n" + rte.getMessage());
			}
		}
		return true;
	}
	
	/**
	 * Extracts all worlds and their values from a String, and puts them into worldList
	 * @param worlds The String to be searched
	 * @return The success of the extraction
	 */
	private boolean extractWorlds(String worlds) {
		Util.debug("Received worlds: " + worlds);
		String temp = worlds.replace('\n', ' ');
		String myWorlds[] = temp.split("[()]");
		for (int  i = 0; i + 1 < myWorlds.length; i += 2) {
			if (myWorlds[i].trim() == "") {
				Util.debug("Encountered an empty world name");
				return false;
			}
			World world = new World(myWorlds[i].trim());
			if (myWorlds[i+1].trim() == "") {
				continue; //no values, don't try to add them.
			}
			String values[] = myWorlds[i+1].split(",");
			for (String value : values) {
				if (value.trim() == "") {
					Util.debug("Encountered an empty value for world: " + world.getName());
					return false;
				}
				try {
					world.addProposition(value.trim());
				}
				catch (RuntimeException rte) {
					throw new RuntimeException("While trying to add \"" + value.trim() + 
							"\" to Wold: \"" + world.getName() + "\"\n" + rte.getMessage());
				}
			}
			state.addWorld(world);
		}
		return true;
	}

	/**
	 * Extract all relations from a String, relations are grouped per agent. Then add them to relationList.
	 * @param relations The String to be read
	 * @return the success of this operation
	 */
	private boolean extractRelations(String relations) {
		Util.debug("Received relations: " + relations);
		String lines[] = relations.split("\n");
		Agent agent = null;
		World toWorld[] = null;
		for (String line : lines) {
			// empty lines exist between entries only.
			if (line.trim().equals("")) {
				agent = null;
				toWorld = null;
				continue; 
			}
			// only if there is no agent are we expecting the next (non empty) line to be an agent name
			// an implication of this is that the entire block will be skipped if there is no such agent.
			if (agent == null) {
				agent = state.getAgent(line.trim());
				continue;
			}
			// fill "toWorld" array if it is null (logically the next line after the agent)
			if (toWorld == null) {
				String worlds[] = line.split(",");
				toWorld = new World[worlds.length];
				for (int i = 0; i < worlds.length; i++) {
					toWorld[i] = state.getWorld(worlds[i].trim());
					if (toWorld[i] == null) {
						Util.debug("Can not find a to world named: " + worlds[i] + "for agent: " + agent.getName());
						return false;
					}
				}
				// Add a reflexive relation for every world not included for this agent in the input file.
				ArrayList<World> allWorlds = state.getWorlds();
				for (World myWorld : toWorld) {
					allWorlds.remove(myWorld);
				}
				for (World allWorld : allWorlds) {
					state.addRelation(new Relation(agent, allWorld, allWorld));	
				}
				continue;
			}

			// each (non empty) next line is of the form "fromWorld, relation, relation.... "
			String words[] = line.split(",");
			World fromWorld = state.getWorld(words[0].trim());
			if (fromWorld == null) {
				Util.debug("Can not find a from world named: " + words[0] + "for agent: " + agent.getName());
				return false;
			}
			if (words.length - 1 != toWorld.length) {
				Util.debug("mismatch on entries on for agent: " + agent.getName() + " on world: " + fromWorld.getName());
				return false;
			}
			for (int i = 1; i < words.length; i++) {
				if (words[i].trim().equalsIgnoreCase("X")) {
					// -1 because the toWorld list is one shorter than this line (it has no from).
					state.addRelation(new Relation(agent, fromWorld, toWorld[i - 1]));
				}
				else {
					continue;
				}
			}
		}
		return true;
	}
}
